package api

import (
	"bytes"
	"github.com/chicken-team-outside/chicken_transmission/proxy_server/email_server"
	"github.com/chicken-team-outside/chicken_transmission/proxy_server/model"
	"github.com/chicken-team-outside/chicken_transmission/proxy_server/setting"
	"github.com/chicken-team-outside/chicken_transmission/proxy_server/utils"
	"github.com/dchest/captcha"
	"github.com/gin-gonic/gin"
	"github.com/go-macaron/cache"
	"go.uber.org/zap"
)

type customCaptchaStore struct {
	captchaCache cache.Cache
}

var emailCaptchaCache cache.Cache

const registerEmailCaptchaKeyPrefix = "register_captcha_"
const resetPasswordEmailCaptchaKeyPrefix = "reset_password_captcha_"

func (c customCaptchaStore) Set(id string, digits []byte) {
	if err := c.captchaCache.Put(id, digits, int64(setting.ImgCaptchaMaxAge)); err != nil {
		zap.L().Warn("failed to set captcha cache", zap.Error(err), zap.String("id", id))
	}
}

func (c customCaptchaStore) Get(id string, clear bool) (digits []byte) {
	v := c.captchaCache.Get(id)
	defer func() {
		if clear {
			if err := c.captchaCache.Delete(id); err != nil {
				zap.L().Warn("failed to delete captcha cache", zap.Error(err), zap.String("id", id))
			}
		}
	}()
	if v != nil {
		return v.([]byte)
	}
	return nil
}

func initV1Captcha(group *gin.RouterGroup) {
	captchaCache := cache.NewMemoryCacher()
	if err := captchaCache.StartAndGC(cache.Options{}); err != nil {
		zap.L().Fatal("failed to start captcha cache", zap.Error(err))
	}
	emailCaptchaCache = cache.NewMemoryCacher()
	if err := emailCaptchaCache.StartAndGC(cache.Options{}); err != nil {
		zap.L().Fatal("failed to start register email captcha cache", zap.Error(err))
	}
	captcha.SetCustomStore(customCaptchaStore{
		captchaCache: captchaCache,
	})
	group.GET("/img", createImgCaptcha)
	group.POST("/register", sendRegisterEmailCaptcha)
	group.POST("/reset_password", sendResetPasswordEmailCaptcha)
}

func createImgCaptcha(ctx *gin.Context) {
	id := captcha.NewLen(4)
	var content bytes.Buffer
	err := captcha.WriteImage(&content, id, 125, 45)
	if err != nil {
		zap.L().Error("failed to create captcha image", zap.Error(err), utils.UnknownError(ctx))
		return
	}
	utils.Success(ctx, "success", gin.H{
		"img": content.Bytes(),
		"cid": id,
	})
}

func verifyImgCaptcha(cid string, answer string) bool {
	return captcha.VerifyString(cid, answer)
}

type SendEmailCaptchaQuery struct {
	Cid    string `json:"cid"`
	Answer string `json:"answer"`
	Email  string `json:"email"`
}

func sendEmailCaptcha(ctx *gin.Context, op string, keyPrefix string, checkFunc func(query *SendEmailCaptchaQuery) bool) {
	query := &SendEmailCaptchaQuery{}
	err := ctx.ShouldBindJSON(query)
	if err != nil {
		zap.L().Info("failed to convert query", zap.Error(err))
		utils.BadRequest(ctx, "invalid params")
		return
	}
	if query.Email == "" || query.Answer == "" || query.Cid == "" {
		utils.BadRequest(ctx, "email, answer, cid are required")
		return
	}
	if !verifyImgCaptcha(query.Cid, query.Answer) {
		utils.BadRequest(ctx, "wrong captcha answer")
		return
	}
	if !checkFunc(query) {
		return
	}
	code := utils.RandomDigits(6)
	err = emailCaptchaCache.Put(keyPrefix+query.Email, code, int64(setting.EmailCaptchaMaxAge))
	if err != nil {
		zap.L().Error("failed to put email captcha to cache", zap.Error(err), utils.UnknownError(ctx))
		return
	}
	err = email_server.SendVerifyCode(query.Email, op, code, setting.EmailCaptchaMaxAge)
	if err != nil {
		zap.L().Error("failed to send email captcha", zap.Error(err), utils.UnknownError(ctx))
		return
	}
	utils.Success(ctx, "success", gin.H{
		"cid": "",
	})
}

func sendRegisterEmailCaptcha(ctx *gin.Context) {
	sendEmailCaptcha(ctx, "注册", registerEmailCaptchaKeyPrefix, func(query *SendEmailCaptchaQuery) bool {
		if model.ExistsEmail(query.Email) {
			utils.BadRequest(ctx, "email already registered!")
			return false
		}
		return true
	})
}

func sendResetPasswordEmailCaptcha(ctx *gin.Context) {
	sendEmailCaptcha(ctx, "重置密码", resetPasswordEmailCaptchaKeyPrefix, func(query *SendEmailCaptchaQuery) bool {
		if !model.ExistsEmail(query.Email) {
			utils.BadRequest(ctx, "email not registered!")
			return false
		}
		return true
	})
}

func removeRegisterEmailCaptcha(email string) {
	if err := emailCaptchaCache.Delete(registerEmailCaptchaKeyPrefix + email); err != nil {
		zap.L().Error("failed to remove email captcha cache", zap.Error(err), zap.String("email", email))
	}
}

func verifyRegisterEmailCaptcha(email string, answer string) bool {
	a := emailCaptchaCache.Get(registerEmailCaptchaKeyPrefix + email)
	if a != nil {
		return a.(string) == answer
	}
	return false
}

func removeResetPasswordEmailCaptcha(email string) {
	if err := emailCaptchaCache.Delete(resetPasswordEmailCaptchaKeyPrefix + email); err != nil {
		zap.L().Error("failed to remove email captcha cache", zap.Error(err), zap.String("email", email))
	}
}

func verifyResetPasswordEmailCaptcha(email string, answer string) bool {
	a := emailCaptchaCache.Get(resetPasswordEmailCaptchaKeyPrefix + email)
	if a != nil {
		return a.(string) == answer
	}
	return false
}
